Skip to content

copier: parallel chunked copy step under the table lock (CO-4, LK-3) - #128

Draft
Kiran01bm wants to merge 1 commit into
kiran01bm/cs5-copierfrom
kiran01bm/cs5-copy-step
Draft

Kiran01bm wants to merge 1 commit into
kiran01bm/cs5-copierfrom
kiran01bm/cs5-copy-step

Conversation

@Kiran01bm

Copy link
Copy Markdown
Collaborator

Adds copier.Copier, the parallel chunked copy from a proven source into its built shadow, and promotes the in-transaction table-lock confirmation into pkg/dbconn so the copier and the shadow builder share it.

Why

The chunker (previous PR in this stack) cuts consecutive key ranges but nothing copied them. The applier that follows needs more than a watermark: with several workers, chunks land out of order, so a captured change must be judged per key as uncut (discard), in flight (defer), or landed (apply). Only the copier knows which chunks are in flight, so it owns that answer.

The shadow builder already re-asserted the lock from inside its own transaction; the copier needs the identical check on every chunk connection. One implementation in dbconn keeps LK-1 enforced in one place.

What

  • pkg/dbconn: (*TableLockSession).Confirm(ctx, conn) — nobody holds the lock → ErrInvariantViolation wrapping the new ErrTableLockNotHeld; another backend holds it → *TableLockHeldError naming it. schemachange.confirmTableLock now maps those onto its existing CauseLockUnconfirmed / CauseLockHeldElsewhere refusals (existing tests unchanged).
  • pkg/copier:
    • Shadow interface (schema, source, shadow, both OIDs, copy columns) — the shape schemachange.BuiltShadow satisfies, so the copier stays importable by the builder. NewCopier refuses a shadow that is not the target's (ST-6) and a lock session that is missing, for another table, or already lost (LK-1).
    • Copier.Run(ctx, pool): N workers (default 4) under the lock session's Bind context. Each chunk runs in its own transaction: SET LOCAL lock_timeout/statement_timeout, SET LOCAL ROLE owner, lock.Confirm, re-resolve both relation OIDs against the proof, then one frozen statement INSERT INTO shadow (cols) SELECT cols FROM source WHERE pk BETWEEN $1::bigint AND $2::bigint ON CONFLICT (pk) DO NOTHING. Elapsed time from an injected progress.Clock feeds Chunker.Feedback (D12).
    • Ledger + Position: a chunk is registered in flight before its transaction begins and removed only after commit or rollback; the watermark advances over the contiguous landed prefix; Position.Classify(key) returns KeyUncut / KeyInFlight / KeyLanded — the CO-4 rule the applier will call. Run returns only after every worker has exited (LK-3), then fails closed if anything is still in flight or the key space is not covered.
  • Docs in the same PR: SAFETY.md copier row, invariants.md CO-4 / LK-1 / LK-3 Enforced today, design package map (dbconn, copier) and D12, architecture.md.

Tests (real PostgreSQL, PG 14/16/18): whole-table copy with 3 workers and 700-row chunks converges via testutil.AssertConverged; a pre-existing shadow row is never overwritten; resume from a watermark copies only the keys above it; cancellation with one chunk pinned mid-insert by an uncommitted shadow row returns context.Canceled with nothing in flight, every key ≤ watermark present, and a resumed copier converges; lock loss mid-copy returns ErrInvariantViolation wrapping the session's loss; a dropped-and-recreated shadow or source is refused (ST-6) with zero rows written; a gone or rival-held lock is refused with zero rows written; the copy SQL is frozen as an exact string (TM-2); pure ledger tests for out-of-order landing, release-without-landing, and resume.

Before / after

Before                                   After
┌──────────┐                             ┌──────────┐  Next()   ┌──────────────────────┐
│ Chunker  │  cuts ranges, nobody        │ Chunker  │◀─────────▶│ Copier (N workers)   │
│ Next/Cut │  copies them                │          │ Feedback  │  claim → tx → land   │
└──────────┘                             └──────────┘           └──────────┬───────────┘
                                                                 per chunk │ SET LOCAL … ROLE
┌──────────────┐ confirmTableLock          ┌──────────────┐               │ lock.Confirm   (LK-1)
│ schemachange │ (own pg_locks query)      │ dbconn       │◀──────────────┤ OID check      (ST-6)
└──────────────┘                           │ Confirm()    │               │ INSERT … DO NOTHING
                                           └──────▲───────┘               ▼
                                                  │ maps to causes   ┌──────────┐
                                           ┌──────┴───────┐          │ Position │ Watermark, Cut,
                                           │ schemachange │          │ Classify │ InFlight → applier
                                           └──────────────┘          └──────────┘

References

  • Stack: base is the chunker PR (kiran01bm/cs5-copier); next PR adds the progress fillers.
  • docs/invariants.md CO-4, LK-1, LK-3; docs/copy-and-swap-design.md D12 and the package map.

🤖 Drafted with Amp (Claude Opus 4.6); reviewed and edited by the author.

The chunker could cut ranges but nothing copied them; the applier needs
a per-key uncut/in-flight/landed answer that only the copier can give.
Promotes the in-transaction lock check to dbconn so copier and builder
share one LK-1 confirmation.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant